import gugga.events.EventDispatcher;
import mx.utils.Delegate;

import com.mosesSupposes.fuse.PennerEasing;

import gugga.animations.AnimationSubjectCacheAsBitmapPolicy;
import gugga.animations.IAnimation;
import gugga.animations.PropertiesTweenAnimationType;
import gugga.collections.HashTable;
import gugga.debug.Assertion;
import gugga.tween.AbstractTween;
import gugga.tween.FrameTween;
import gugga.tween.TimeTween;
import gugga.tween.TweensManager;
import gugga.utils.DebugUtils;
import gugga.utils.ReflectUtil;

[Event("start")]
[Event("interrupted")]
[Event("completed")]

/**
 * @author todor
 */
class gugga.animations.PropertiesTweenAnimation extends EventDispatcher implements IAnimation 
{	
	private var mTween:AbstractTween;
	
	private var mIsRunning:Boolean;
	
	private var mSubject:Object;
	public function get Subject() : Object { return mSubject; }
	public function set Subject(aValue:Object) : Void { mSubject = aValue; }
	
	private var mTweeningProprties:Array;
	public function get TweeningProprties() : Array { return mTweeningProprties; }
	public function set TweeningProprties(aValue:Array) : Void { mTweeningProprties = aValue; }
	
	//TODO: Remove. Make mTweeningProprties a HashTable.
	private var mTweenToValues:Array;
	public function get TweenToValues() : Array { return mTweenToValues; }
	public function set TweenToValues(aValue:Array) : Void { mTweenToValues = aValue; }
	
	private var mEasingEquation:Function;
	public function get EasingEquation() : Function { return mEasingEquation; }
	public function set EasingEquation(aValue:Function) : Void { mEasingEquation = aValue; }
	
	//TODO: rename to Duration
	private var mTweenTime:Number;
	public function get TweenTime() : Number { return mTweenTime; }
	public function set TweenTime(aValue:Number) : Void 
	{ 
		mTweenTime = aValue;
		roundTimeForFrameTween();
	}
	
	private var mTweenType : PropertiesTweenAnimationType;
	public function get TweenType() : PropertiesTweenAnimationType { return mTweenType; }
	public function set TweenType(aValue:PropertiesTweenAnimationType) : Void 
	{ 
		mTweenType = aValue;
		roundTimeForFrameTween(); 
	}
	
	private var mOldSubjectCacheAsBitmatValue : Boolean;
	
	private var mSubjectCacheAsBitmapPolicyActual : AnimationSubjectCacheAsBitmapPolicy;
	private var mSubjectCacheAsBitmapPolicy : AnimationSubjectCacheAsBitmapPolicy;
	public function get SubjectCacheAsBitmapPolicy() : AnimationSubjectCacheAsBitmapPolicy { return mSubjectCacheAsBitmapPolicy; }
	public function set SubjectCacheAsBitmapPolicy(aValue:AnimationSubjectCacheAsBitmapPolicy) : Void { mSubjectCacheAsBitmapPolicy = aValue; }
	
	private var mTweenUpdateDelegate : Function;
	public function get TweenUpdateDelegate() : Function { return mTweenUpdateDelegate; }
	public function set TweenUpdateDelegate(aValue:Function) : Void { mTweenUpdateDelegate = aValue; }
	
	private var mCuePoints:Array;
	
	public function getCuePointEventNames() : Array 
	{
		var result : Array = new Array();
		for (var i : Number = 0; i < mCuePoints.length; i++)
		{
			result.push(mCuePoints[i].event);
		}
		return result;
	}
	
	public function PropertiesTweenAnimation()
	{
		mIsRunning = false;
		mCuePoints = new Array();
		
		mTweenType = PropertiesTweenAnimationType.FrameTween;
		mSubjectCacheAsBitmapPolicy = AnimationSubjectCacheAsBitmapPolicy.Auto;
	}
	
	public function start() : Void 
	{
		if(!mIsRunning)
		{
			mIsRunning = true;
			
			//TODO: change interface to accept HashTable of tweeningProperties
			var tweeningProperties : HashTable = new HashTable();
			for (var i:Number = 0; i < mTweeningProprties.length; i++)
			{
				tweeningProperties[mTweeningProprties[i]] = mTweenToValues[i];
			}
			
			mOldSubjectCacheAsBitmatValue = MovieClip(mSubject).cacheAsBitmap;
			determineActualSubjectCacheAsBitmapPolicy(tweeningProperties);
			
			if(mSubjectCacheAsBitmapPolicyActual == AnimationSubjectCacheAsBitmapPolicy.ForceTrueWhileAnimating)
			{
				MovieClip(mSubject).cacheAsBitmap = true;
			}
			else if(mSubjectCacheAsBitmapPolicyActual == AnimationSubjectCacheAsBitmapPolicy.ForceFalseWhileAnimating)
			{
				MovieClip(mSubject).cacheAsBitmap = false;
			}
			
			if(mTweenType == PropertiesTweenAnimationType.FrameTween)
			{
				mTween = new FrameTween(mSubject, tweeningProperties);
			}
			else if(mTweenType == PropertiesTweenAnimationType.TimeTween)
			{
				mTween = new TimeTween(mSubject, tweeningProperties);
			}
			else
			{
				Assertion.warning("Invalid tween type", this, arguments);
			}
			
			mTween.Duration = mTweenTime;
			mTween.EasingEquation = mEasingEquation;
			mTween.TweenUpdateDelegate = Delegate.create(this, onTweenUpdate);
			mTween.TweenFinishedDelegate = Delegate.create(this, onTweenFinished);
			
			TweensManager.Instance.doTween(mTween);
			
			onTweenStarted();
		}
	}
	
	private function determineActualSubjectCacheAsBitmapPolicy(aTweeningProperties : HashTable) : Void
	{
		if(mSubjectCacheAsBitmapPolicy == AnimationSubjectCacheAsBitmapPolicy.Auto)
		{
			if(mSubject instanceof MovieClip)
			{
				if (aTweeningProperties.containsKey("_x") || aTweeningProperties.containsKey("x") ||
					aTweeningProperties.containsKey("_y") || aTweeningProperties.containsKey("y"))
				{
					mSubjectCacheAsBitmapPolicyActual = AnimationSubjectCacheAsBitmapPolicy.ForceTrueWhileAnimating;
				}
				else
				{
					mSubjectCacheAsBitmapPolicyActual = AnimationSubjectCacheAsBitmapPolicy.Leave;
				}
				
				if (aTweeningProperties.containsKey("_width") || aTweeningProperties.containsKey("width") ||
					aTweeningProperties.containsKey("_height") || aTweeningProperties.containsKey("height") ||
					aTweeningProperties.containsKey("_alpha") || aTweeningProperties.containsKey("_rotation") ||
					aTweeningProperties.containsKey("_xscale") || aTweeningProperties.containsKey("_yscale"))
				{
					mSubjectCacheAsBitmapPolicyActual = AnimationSubjectCacheAsBitmapPolicy.ForceFalseWhileAnimating;
				}
				else
				{
					mSubjectCacheAsBitmapPolicyActual = AnimationSubjectCacheAsBitmapPolicy.Leave;
				}
			}
			else
			{
				mSubjectCacheAsBitmapPolicyActual = AnimationSubjectCacheAsBitmapPolicy.Leave;
			}
		}
		else
		{
			mSubjectCacheAsBitmapPolicyActual = mSubjectCacheAsBitmapPolicy;
		}
	}
	
	public function isImmediatelyInterruptable() : Boolean
	{
		return true;
	}
	
	public function interrupt() : Void
	{
		mIsRunning = false;
		
		if(mTween)
		{
			TweensManager.Instance.stopTween(mTween);
		}
		
		if(mSubjectCacheAsBitmapPolicyActual != AnimationSubjectCacheAsBitmapPolicy.Leave)
		{
			MovieClip(mSubject).cacheAsBitmap = mOldSubjectCacheAsBitmatValue;
		}
		
		dispatchEvent({type:"interrupted", target:this});
	}
	
	public function isRunning() : Boolean 
	{
		return mIsRunning;
	}
	
	public function addCuePoint(aPropertyValueReached:Number, aEventName:String):Void
	{
		mCuePoints.push({position:aPropertyValueReached, event:aEventName});
		mCuePoints.sortOn("position");
	}
	
	/**
	 * DEPRECATED! If aTweenType not provided and default Tween Type is FrameTween, 
	 * aTweenTime is multiplied by 20.
	 */
	public function setTween(aSubject:Object, aTweeningProprties:Array, aTweenToValues:Array, 
		aTweenTime:Number, aEasingEquation:Function, aTweenType : PropertiesTweenAnimationType) : Void
	{
		mSubject = aSubject;
		mTweeningProprties = aTweeningProprties;
		mTweenToValues = aTweenToValues;
		mTweenTime = aTweenTime;
		mEasingEquation = aEasingEquation;
		
		if(aTweenType)
		{
			mTweenType = aTweenType;
			roundTimeForFrameTween();
		}
		else
		{ 
			//if default is PropertiesTweenAnimationType.FrameTween
			//TODO: remove in future when setTween() is made private
			if(mTweenType == PropertiesTweenAnimationType.FrameTween)
			{
				mTweenTime = Math.round(aTweenTime * 20);
			}
		}
	}	
	
	private function roundTimeForFrameTween() : Void
	{
		if(mTweenType == PropertiesTweenAnimationType.FrameTween)
		{
			mTweenTime = Math.round(mTweenTime);
		}
	}
	
	public function setFrameTween(aSubject:Object, aTweeningProprties:Array, aTweenToValues:Array, 
		aTweenTime:Number, aEasingEquation:Function) : Void
	{
		setTween(aSubject, aTweeningProprties, aTweenToValues, 
			aTweenTime, aEasingEquation, PropertiesTweenAnimationType.FrameTween);
	} 

	public function setTimeTween(aSubject:Object, aTweeningProprties:Array, aTweenToValues:Array, 
		aTweenTime:Number, aEasingEquation:Function) : Void
	{
		setTween(aSubject, aTweeningProprties, aTweenToValues, 
			aTweenTime, aEasingEquation, PropertiesTweenAnimationType.TimeTween);
	} 
	
	/**
	 * DEPRECATED!!!
	 */
	public function setFadeInTween(aSubject:Object, aTweenTime:Number, aEasingEquation:Function)
	{
		setTween(aSubject, ["_alpha"], [100], aTweenTime, aEasingEquation);
	}		

	/**
	 * DEPRECATED!!!
	 */
	public function setFadeOutTween(aSubject:Object, aTweenTime:Number, aEasingEquation:Function)
	{
		setTween(aSubject, ["_alpha"], [0], aTweenTime, aEasingEquation);
	}	
	
	//TODO: should we have these function. if yes, why not create more like them
	public function setFadeInFrameTween(aSubject:Object, aTweenFrames:Number, aEasingEquation:Function)
	{
		setFrameTween(aSubject, ["_alpha"], [100], aTweenFrames, aEasingEquation);
	}		

	//TODO: should we have these function. if yes, why not create more like them
	public function setFadeOutFrameTween(aSubject:Object, aTweenFrames:Number, aEasingEquation:Function)
	{
		setFrameTween(aSubject, ["_alpha"], [0], aTweenFrames, aEasingEquation);
	}	
	
	//TODO: should we have these function. if yes, why not create more like them
	public function setFadeInTimeTween(aSubject:Object, aTweenTime:Number, aEasingEquation:Function)
	{
		setTimeTween(aSubject, ["_alpha"], [100], aTweenTime, aEasingEquation);
	}		

	//TODO: should we have these function. if yes, why not create more like them
	public function setFadeOutTimeTween(aSubject:Object, aTweenTime:Number, aEasingEquation:Function)
	{
		setTimeTween(aSubject, ["_alpha"], [0], aTweenTime, aEasingEquation);
	}	
	
	private function onTweenStarted()
	{
		dispatchEvent({type:"start", target:this});
	}

	private function onTweenUpdate()
	{
		mTweenUpdateDelegate();
		
		if(mCuePoints.length > 0)
		{
			var currentPosition:Number = mSubject[mTweeningProprties[0]];
			
			for(var i:Number = mCuePoints.length - 1; i >= 0; i--)
			{
				if(currentPosition >= mCuePoints[i].position)
				{
					dispatchEvent({type:mCuePoints[i].event, target:this});
					break;
				}
			}
		}
	}

	private function onTweenFinished()
	{
		mIsRunning = false;
		
		if(mSubjectCacheAsBitmapPolicyActual != AnimationSubjectCacheAsBitmapPolicy.Leave)
		{
			MovieClip(mSubject).cacheAsBitmap = mOldSubjectCacheAsBitmatValue;
		}
		
		dispatchEvent({type:"completed", target:this});
	}
}